#ifndef LOCK_FREE_RING_H
#define LOCK_FREE_RING_H
#include <atomic>

template <typename T, size_t SIZE>
class lock_free_ring
{
public:
    lock_free_ring()
        : cons_tail_(SIZE - 1), cons_head_(SIZE - 1), size_(SIZE)
    {
    }

    void init()
    {
        prod_head_.store(0, std::memory_order_relaxed);
        prod_tail_.store(0, std::memory_order_relaxed);
        cons_head_.store(SIZE - 1, std::memory_order_relaxed);
        cons_tail_.store(SIZE - 1, std::memory_order_relaxed);
        size_ = SIZE;
    }

    bool push(const T &value)
    {
        auto prod_head = prod_head_.load(std::memory_order_relaxed);
        size_t new_prod_head = 0;
        size_t cons_tail = 0;

        do
        {
            std::atomic_thread_fence(std::memory_order_acquire);
            cons_tail = cons_tail_.load(std::memory_order_acquire);
            new_prod_head = (prod_head + 1) % size_;
            if (new_prod_head == cons_tail || prod_head == cons_tail)
                return false;
        } while (!prod_head_.compare_exchange_strong(prod_head, new_prod_head, std::memory_order_relaxed, std::memory_order_relaxed));

        data_[prod_head] = value;

        while (prod_tail_.load(std::memory_order_relaxed) != prod_head)
            __builtin_ia32_pause();

        prod_tail_.store(new_prod_head, std::memory_order_release);
        return true;
    }

    bool pop(T &value)
    {
        size_t new_cons_head = 0;
        auto cons_head = cons_head_.load(std::memory_order_relaxed);
        size_t prod_tail = 0;

        do
        {
            std::atomic_thread_fence(std::memory_order_acquire);
            prod_tail = prod_tail_.load(std::memory_order_acquire);
            new_cons_head = (cons_head + 1) % size_;
            if (new_cons_head == prod_tail)
                return false;
        } while (!cons_head_.compare_exchange_strong(cons_head, new_cons_head, std::memory_order_relaxed, std::memory_order_relaxed));

        value = data_[new_cons_head];

        while (cons_tail_.load(std::memory_order_relaxed) != cons_head)
            __builtin_ia32_pause();

        cons_tail_.store(new_cons_head, std::memory_order_release);
        return true;
    }

    void print_info()
    {
        std::cout << "prod_tail_:" << prod_tail_.load(std::memory_order_relaxed)
                  << ", prod_head_:" << prod_head_.load(std::memory_order_relaxed)
                  << ", cons_tail_:" << cons_tail_.load(std::memory_order_relaxed)
                  << ", cons_head_:" << cons_head_.load(std::memory_order_relaxed)
                  << ", size_:" << size_ << std::endl;

        std::cout << "data: [";
        for (size_t i = 0; i < size_; ++i)
        {
            std::cout << data_[i] << ",";
            if (i % 20 == 0)
            {
                std::cout << std::endl;
            }
        }
        std::cout << "]" << std::endl;
    }

private:
    std::atomic<size_t> prod_tail_{0};
    std::atomic<size_t> prod_head_{0};
    char cache_line0_[0] __attribute__((aligned(64)));
    std::atomic<size_t> cons_tail_{0};
    std::atomic<size_t> cons_head_{0};
    char cache_line1_[0] __attribute__((aligned(64)));
    T data_[SIZE] = {0};
    size_t size_{0};

    std::mutex mutex;
};

inline static constexpr size_t QUEUE_SIZE = 100000;
inline static constexpr size_t TOTAL_COUNT = 1000000;
inline static constexpr size_t push_thread_cnt = 2;
inline static constexpr size_t pop_thread_cnt = 5;

struct lock_free_ring_test
{
    lock_free_ring<size_t, QUEUE_SIZE> ring;
    std::atomic<bool> stop = false;
    std::atomic<bool> finish = false;
};

#endif